Italiano

Una guida completa alle Server Actions di Next.js 14, che copre le migliori pratiche per la gestione dei moduli, la validazione dei dati, la sicurezza e tecniche avanzate per creare applicazioni web moderne.

Server Actions di Next.js 14: Padroneggiare le Migliori Pratiche per la Gestione dei Moduli

Next.js 14 introduce potenti funzionalità per la creazione di applicazioni web performanti e user-friendly. Tra queste, le Server Actions si distinguono come un modo trasformativo per gestire l'invio di moduli e le mutazioni dei dati direttamente sul server. Questa guida offre una panoramica completa delle Server Actions in Next.js 14, concentrandosi sulle migliori pratiche per la gestione dei moduli, la validazione dei dati, la sicurezza e le tecniche avanzate. Esploreremo esempi pratici e forniremo spunti concreti per aiutarti a costruire applicazioni web robuste e scalabili.

Cosa sono le Server Actions di Next.js?

Le Server Actions sono funzioni asincrone che vengono eseguite sul server e possono essere invocate direttamente dai componenti React. Eliminano la necessità di tradizionali route API per la gestione dell'invio di moduli e delle mutazioni dei dati, risultando in un codice semplificato, una sicurezza migliorata e prestazioni ottimizzate. Le Server Actions sono React Server Components (RSC), il che significa che vengono eseguite sul server, portando a caricamenti iniziali delle pagine più rapidi e una migliore SEO.

Vantaggi Chiave delle Server Actions:

Configurazione del Tuo Progetto Next.js 14

Prima di immergerti nelle Server Actions, assicurati di avere un progetto Next.js 14 configurato. Se stai iniziando da zero, crea un nuovo progetto usando il seguente comando:

npx create-next-app@latest my-next-app

Assicurati che il tuo progetto utilizzi la struttura di directory app per sfruttare appieno i Server Components e le Actions.

Gestione di Base dei Moduli con le Server Actions

Iniziamo con un esempio semplice: un modulo che invia dati per creare un nuovo elemento in un database. Useremo un semplice modulo con un campo di input e un pulsante di invio.

Esempio: Creazione di un Nuovo Elemento

Per prima cosa, definisci una funzione Server Action all'interno del tuo componente React. Questa funzione gestirà la logica di invio del modulo sul server.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simula interazione con il database
  console.log('Creazione elemento:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula latenza

  console.log('Elemento creato con successo!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    await createItem(formData);
    setIsSubmitting(false);
  }

  return (
    
); }

Spiegazione:

Validazione dei Dati

La validazione dei dati è cruciale per garantire l'integrità dei dati e prevenire vulnerabilità di sicurezza. Le Server Actions offrono un'eccellente opportunità per eseguire la validazione lato server. Questo approccio aiuta a mitigare i rischi associati alla sola validazione lato client.

Esempio: Validazione dei Dati di Input

Modifica la Server Action createItem per includere la logica di validazione.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  if (!name || name.length < 3) {
    throw new Error('Il nome dell\'elemento deve essere lungo almeno 3 caratteri.');
  }

  // Simula interazione con il database
  console.log('Creazione elemento:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula latenza

  console.log('Elemento creato con successo!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Si è verificato un errore.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Spiegazione:

Utilizzo di Librerie di Validazione

Per scenari di validazione più complessi, considera l'utilizzo di librerie di validazione come:

Ecco un esempio che utilizza Zod:

// app/utils/validation.ts
import { z } from 'zod';

export const CreateItemSchema = z.object({
  name: z.string().min(3, 'Il nome dell\'elemento deve essere lungo almeno 3 caratteri.'),
});
// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  const validatedFields = CreateItemSchema.safeParse({ name });

  if (!validatedFields.success) {
    return { errors: validatedFields.error.flatten().fieldErrors };
  }

  // Simula interazione con il database
  console.log('Creazione elemento:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula latenza

  console.log('Elemento creato con successo!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Si è verificato un errore.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Spiegazione:

Considerazioni sulla Sicurezza

Le Server Actions migliorano la sicurezza eseguendo il codice sul server, ma è comunque cruciale seguire le migliori pratiche di sicurezza per proteggere la tua applicazione dalle minacce comuni.

Prevenzione del Cross-Site Request Forgery (CSRF)

Gli attacchi CSRF sfruttano la fiducia che un sito web ha nel browser di un utente. Per prevenire gli attacchi CSRF, implementa meccanismi di protezione CSRF.

Next.js gestisce automaticamente la protezione CSRF quando si utilizzano le Server Actions. Il framework genera e convalida un token CSRF per ogni invio di modulo, garantendo che la richiesta provenga dalla tua applicazione.

Gestione dell'Autenticazione e Autorizzazione Utente

Assicurati che solo gli utenti autorizzati possano eseguire determinate azioni. Implementa meccanismi di autenticazione e autorizzazione per proteggere dati e funzionalità sensibili.

Ecco un esempio che utilizza NextAuth.js per proteggere una Server Action:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';

async function createItem(formData: FormData) {
  'use server'

  const session = await getServerSession(authOptions);

  if (!session) {
    throw new Error('Non autorizzato');
  }

  const name = formData.get('name') as string;

  // Simula interazione con il database
  console.log('Creazione elemento:', name, 'dall\'utente:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula latenza

  console.log('Elemento creato con successo!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Si è verificato un errore.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Spiegazione:

Sanificazione dei Dati di Input

Sanifica i dati di input per prevenire attacchi di Cross-Site Scripting (XSS). Gli attacchi XSS si verificano quando codice dannoso viene iniettato in un sito web, compromettendo potenzialmente i dati degli utenti o le funzionalità dell'applicazione.

Usa librerie come DOMPurify o sanitize-html per sanificare l'input fornito dall'utente prima di elaborarlo nelle tue Server Actions.

Tecniche Avanzate

Ora che abbiamo coperto le basi, esploriamo alcune tecniche avanzate per utilizzare efficacemente le Server Actions.

Aggiornamenti Ottimistici

Gli aggiornamenti ottimistici offrono una migliore esperienza utente aggiornando immediatamente l'interfaccia utente come se l'azione avesse successo, anche prima che il server lo confermi. Se l'azione fallisce sul server, l'interfaccia utente viene ripristinata allo stato precedente.

// app/components/UpdateItemForm.tsx
'use client';

import { useState } from 'react';

async function updateItem(id: string, formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simula interazione con il database
  console.log('Aggiornamento elemento:', id, 'con nome:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula latenza

  // Simula fallimento (a scopo dimostrativo)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Aggiornamento dell\'elemento non riuscito.');
  }

  console.log('Elemento aggiornato con successo!');
  return { name }; // Restituisci il nome aggiornato
}

export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  const [itemName, setItemName] = useState(initialName);

  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);

    // Aggiorna ottimisticamente l'UI
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      //In caso di successo l'aggiornamento è già riflesso nell'UI tramite setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'Si è verificato un errore.');
      // Ripristina l'UI in caso di errore
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Nome Attuale: {itemName}

{errorMessage &&

{errorMessage}

}
); }

Spiegazione:

Rivalidazione dei Dati

Dopo che una Server Action modifica i dati, potrebbe essere necessario rivalutare i dati memorizzati nella cache per garantire che l'UI rifletta le ultime modifiche. Next.js fornisce diversi modi per rivalutare i dati:

Ecco un esempio di rivalutazione di un percorso dopo aver creato un nuovo elemento:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { revalidatePath } from 'next/cache';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simula interazione con il database
  console.log('Creazione elemento:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula latenza

  console.log('Elemento creato con successo!');

  revalidatePath('/items'); // Rivaluta il percorso /items
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Si è verificato un errore.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Spiegazione:

Migliori Pratiche per le Server Actions

Per massimizzare i benefici delle Server Actions, considera le seguenti migliori pratiche:

Errori Comuni e Come Evitarli

Sebbene le Server Actions offrano numerosi vantaggi, ci sono alcuni errori comuni di cui essere consapevoli:

Conclusione

Le Server Actions di Next.js 14 offrono un modo potente ed efficiente per gestire l'invio di moduli e le mutazioni dei dati direttamente sul server. Seguendo le migliori pratiche delineate in questa guida, puoi costruire applicazioni web robuste, sicure e performanti. Adotta le Server Actions per semplificare il tuo codice, migliorare la sicurezza e l'esperienza utente complessiva. Mentre integri questi principi, considera l'impatto globale delle tue scelte di sviluppo. Assicurati che i tuoi moduli e i processi di gestione dei dati siano accessibili, sicuri e user-friendly per un pubblico internazionale eterogeneo. Questo impegno per l'inclusività non solo migliorerà l'usabilità della tua applicazione, ma ne amplierà anche la portata e l'efficacia su scala globale.

Server Actions di Next.js 14: Padroneggiare le Migliori Pratiche per la Gestione dei Moduli | MLOG